import React, { useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import { Parser } from "acorn";
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution";
import "monaco-editor/esm/vs/language/json/monaco.contribution";
// import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import * as monaco from "monaco-editor/esm/vs/editor/edcore.main";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import "tailwindcss/tailwind.css";
import Tree from "./tree";

self.MonacoEnvironment = {
  getWorker: (_, label) => {
    if (label === "json") {
      return new jsonWorker();
    }

    return editorWorker();
  },
};

function getAst(code) {
  if (code) {
    return Parser.parse(code, {
      ecmaVersion: 2015,
      // locations: true,
    });
  }
}

const Edit = function ({ className, setCode }) {
  const editRef = useRef();
  const [edit, setEdit] = useState();

  useEffect(() => {
    if (editRef.current && !edit) {
      const editIns = monaco.editor.create(editRef.current, {
        value: "",
        language: "javascript",
        mimeType: "application/javascript",
        minimap: {
          enabled: false,
        },
      });
      editIns.onDidChangeModelContent((e) => {
        const code = editIns.getValue();
        setCode(code);
      });
      setEdit(editIns);
    }
  }, [editRef.current]);

  return <div className={`${className || ""}`} ref={editRef}></div>;
};

const JSONView = function ({ className, code }) {
  const editRef = useRef();
  const [edit, setEdit] = useState();
  const [syncError, setSyncError] = useState();
  const [jsonCode, setJsonCode] = useState();

  useEffect(() => {
    let ast;
    try {
      if (code) {
        ast = getAst(code);
        setSyncError(undefined);
      }
    } catch (error) {
      setSyncError(error?.message);
    }
    setJsonCode(ast ? JSON.stringify(ast, undefined, 4) : "");
  }, [code]);

  useEffect(() => {
    if (editRef.current && !edit) {
      const editIns = monaco.editor.create(editRef.current, {
        value: "",
        language: "json",
        mimeType: "application/json",
        minimap: {
          enabled: false,
        },
        readOnly: true,
      });
      setEdit(editIns);
    }
  }, [editRef.current]);

  useEffect(() => {
    if (jsonCode && edit) {
      edit.setValue(jsonCode);
    } else if (syncError && edit) {
      edit.setValue(JSON.stringify(syncError));
    }
  }, [jsonCode, edit]);

  return <div className={`${className || ""}`} ref={editRef}></div>;
};

const TreeView = function ({ className, code }) {
  const astRef = useRef();

  useEffect(() => {
    function renderAst(container, ast) {
      if (ast) {
        var svg = Tree(ast, {
          width: container.clientWidth,
          height: container.clientHeight,
          label: (node) => node.type,
        });
        container.appendChild(svg);
      } else {
        container.innerHTML = "";
      }
    }

    let ast;
    if (code && astRef.current) {
      try {
        ast = getAst(code);
      } catch (error) {}
    }
    renderAst(astRef.current, ast);
  }, [code]);

  return <div className={`${className || ""}`} ref={astRef}></div>;
};

const App = function () {
  const [code, setCode] = useState("");
  return (
    <div className="flex items-stretch h-full">
      <div className="flex-1 flex flex-col border-r border-r-gray-300">
        <Edit className="flex-auto h-1/3 box-border" setCode={setCode} />
        <JSONView
          className="flex-auto h-2/3 border-t-2 border-t-gray-300 box-border"
          code={code}
        />
      </div>
      <TreeView className="flex-1 ml-4" code={code} />
    </div>
  );
};

const root = createRoot(document.getElementById("app"));
root.render(<App />);
